package com.example.workflow.aspect;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.example.workflow.annotation.Resubmit;
import com.example.workflow.exception.ServiceException;
import com.example.workflow.util.ApiRequestKey;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.glassfish.jersey.internal.guava.Maps;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.IntStream;

import static org.springframework.beans.BeanUtils.getParameterNames;

@Aspect
@Component
@Slf4j
public class ResubmitAspect {
    private static final ThreadLocal<String> KEY_HOLDER = new ThreadLocal<>();
    private static final String COMMON_KEY_PREFIX = "WEB:RESUBMIT";
    private static final String COLON = ":";
    private final SpelExpressionParser spelParser = new SpelExpressionParser();

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    // SpEL 解析器
    private static final SpelExpressionParser parser = new SpelExpressionParser();

    // 替换原有的 LocalVariableTableParameterNameDiscoverer
    private static final DefaultParameterNameDiscoverer parameterNameDiscoverer =
            new DefaultParameterNameDiscoverer();
    /**
     * 定义切入点，匹配所有带有{@link Resubmit}注解的地方
     *
     */
    @Pointcut("@annotation(com.example.workflow.annotation.Resubmit)")
    public void resubmitPointcut() {
        // 定义切入点
    }

    @Before("resubmitPointcut() && @annotation(resubmit)")
    public void beforeResubmit(JoinPoint joinPoint,Resubmit resubmit) {
//        System.out.println("进入注解Before");
        try {
            String key = constructUniqueKey(joinPoint,resubmit);
//            System.out.println("key = "+key);

            // 检查redis中是否存在相同key，若存在则拒绝请求
            if(Boolean.TRUE.equals(redisTemplate.hasKey(key))) {
//                throw new ServiceException("请不要频繁重复操作");
                System.out.println("请不要频繁重复操作");
            }

            // 将key存入redis并设置过期时间（以秒为单位）
            long expireTime = Math.max(resubmit.limitation(), resubmit.timeout());
            redisTemplate.opsForValue().set(key,resubmit.key(),expireTime);
            KEY_HOLDER.set(key);
        } catch (Exception e){
            log.error(e.getMessage());
            throw new ServiceException(e.getMessage());
        }
    }
    /**
     * 方法执行后清理线程局部变量中的key
     *
     */
    @After("resubmitPointcut() && @annotation(resubmit)")
    public void afterResubmit(Resubmit resubmit) {
//        System.out.println("退出注解After");
        String key = KEY_HOLDER.get();
        if(key == null||resubmit.limitation()>0) return;
        if(resubmit.limitation() == 0) {
            redisTemplate.delete(key);
        }
        KEY_HOLDER.remove();
    }
    /**
     * 自定义key值
     *
     */
    private String constructUniqueKey(JoinPoint joinPoint, Resubmit resubmit) {
//        System.out.println("constructUniqueKey");
        String customKey = extractCustomKey(joinPoint,resubmit);
        String prefix = buildKeyPrefix(joinPoint,resubmit);

//        if(customKey == null||customKey.isEmpty()) {
//            return "";
//        }

        // 组装完整的key
        return COMMON_KEY_PREFIX + COLON + prefix + COLON + customKey;
    }

    /**
     * 提取自定义参数
     * @param joinPoint
     * @param resubmit
     * @return
     */
    private String extractCustomKey(JoinPoint joinPoint, Resubmit resubmit) {
//        if(resubmit.key().isEmpty()) {
//            return parseExpressionKey(joinPoint,resubmit.key());
//        }
//        // 否则从参数中查找实现了ApiRequestKey接口的对象
//        for (Object arg : joinPoint.getArgs()) {
//            if(arg instanceof ApiRequestKey) {
//                return DigestUtil.md5(((ApiRequestKey) arg).getKey());
//            }
//        }

        // 1. 优先检查SpEL表达式key
//        System.out.println("extractCustomKey");
//        System.out.println(resubmit.key());

//        if (!resubmit.key().isEmpty()) {
//            return parseExpressionKey(joinPoint, resubmit.key());
//        }

//        // 2. 从参数查找ApiRequestKey实现
//        Object[] args = joinPoint.getArgs();
//        if (args != null) {
//            for (Object arg : args) {
//                System.out.println("参数为："+arg);
//                if (arg instanceof ApiRequestKey) {
//                    return new String(DigestUtil.md5(((ApiRequestKey) arg).getKey()), StandardCharsets.UTF_8);
//                }
//            }
//        }

//        // 1. 获取方法参数名和值
//        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
//        System.out.println("method = "+method);
//        String[] paramNames = parameterNameDiscoverer.getParameterNames(method);
//        System.out.println("paramNames = "+paramNames);
//        Object[] args = joinPoint.getArgs();
//        System.out.println("args = "+args);
//
//        // 4. 创建SpEL上下文并设置参数变量
//        StandardEvaluationContext context = new StandardEvaluationContext();
        // 2. 提取表达式内容（去掉${}）
//        String expression = spelExpression.substring(2, spelExpression.length() - 1);
//
//        if (paramNames != null) {
//            for (int i = 0; i < paramNames.length; i++) {
//                context.setVariable(paramNames[i], args[i]); // 如设置 completeOrder=请求体对象
//            }
//        }
//
//        // 5. 执行SpEL表达式，从completeOrder中提取orderId
//        String orderId = spelParser.parseExpression(expression)
//                .getValue(context, String.class);




//        if (spelExpression.startsWith("${") && spelExpression.endsWith("}")) {
//            String expression = spelExpression.substring(2, spelExpression.length() - 1);
//
//            // 3. 创建SpEL上下文并设置变量
//            StandardEvaluationContext context = new StandardEvaluationContext();
//            if (paramNames != null) {
//                for (int i = 0; i < paramNames.length; i++) {
//                    System.out.println("paramNames "+paramNames[i]);
//                    System.out.println("args "+args[i]);
//                    context.setVariable(paramNames[i], args[i]);
//                }
//            }
//
//            // 4. 执行SpEL表达式
//            Object value = parser.parseExpression(expression).getValue(context);
//            String orderId = String.valueOf(value); // 转换为字符串
//
//        }
//            return "";


//        return "";

        if(resubmit.key().isEmpty()) {
            return parseExpressionKey(joinPoint,resubmit.key());
        }
        // 获取方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        // 创建SpEL上下文
        EvaluationContext context = new StandardEvaluationContext();

        // 设置方法参数到上下文中
        String[] paramNames = parameterNameDiscoverer.getParameterNames(method);
        Object[] args = joinPoint.getArgs();

        if (paramNames != null) {
            for (int i = 0; i < paramNames.length; i++) {
                System.out.println("paramNames = "+paramNames[i]+"args = "+args[i]);
                context.setVariable(paramNames[i], args[i]);
            }
        }

        // 解析SpEL表达式
        return parser.parseExpression(resubmit.key())
                .getValue(context, String.class);
    }
    /**
     * 根据注解配置构建key的前缀部分。
     *
     * @param joinPoint 切入点信息
     * @param resubmit  防止重提交的注解配置
     * @return key前缀字符串
     */
    private String buildKeyPrefix(JoinPoint joinPoint, Resubmit resubmit) {
        return switch (resubmit.keyPrefix()) {
            case URL_PATH -> {
                HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
                yield request.getRequestURI().replace("/", COLON) + COLON + request.getMethod().toLowerCase();
            }
            case CLASS -> joinPoint.getSignature().getDeclaringTypeName().toLowerCase();
            case CLASS_METHOD -> {
                MethodSignature signature = (MethodSignature) joinPoint.getSignature();
                yield signature.getDeclaringTypeName().toLowerCase() + COLON + signature.getName().toLowerCase();
            }
            default -> "";
        };
    }

    /**
     * 解析key表达式，支持从方法参数中提取属性值。
     *
     * @param joinPoint      切入点信息
     * @param keyExpression  key表达式字符串
     * @return 解析后的key字符串
     */
    private String parseExpressionKey(JoinPoint joinPoint, String keyExpression) {
        Pattern pattern = Pattern.compile("\\$\\{(.*?)}");
        Matcher matcher = pattern.matcher(keyExpression);

        if (matcher.find()) {
            String groupName = matcher.group(1);
            Object value = BeanUtil.getProperty(obtainMethodArgumentsMap(joinPoint), groupName);
            return value != null ? value.toString() : null;
        }

        return null;
    }

    /**
     * 获取方法参数并转换为Map形式，便于后续处理。
     *
     * @param joinPoint 切入点信息
     * @return 参数映射表
     */
    private Map<String, Object> obtainMethodArgumentsMap(JoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        String[] argNames = methodSignature.getParameterNames();
        Object[] argValues = joinPoint.getArgs();

        Map<String, Object> args = Maps.newHashMapWithExpectedSize(argValues.length);
        for (int i = 0; i < argNames.length; i++) {
            args.put(argNames[i], shouldIgnoreArgument(argValues[i]) ? "[ignore]" : argValues[i]);
        }

        return args;
    }

    /**
     * 判断是否应该忽略某些类型的参数，例如文件上传、HTTP请求/响应等。
     *
     * @param object 参数对象
     * @return 是否忽略该参数
     */
    private boolean shouldIgnoreArgument(Object object) {
        if (object == null) return false;

        Class<?> clazz = object.getClass();
        return clazz.isArray() && IntStream.range(0, Array.getLength(object)).anyMatch(index -> shouldIgnoreArgument(Array.get(object, index)))
                || Collection.class.isAssignableFrom(clazz) && ((Collection<?>) object).stream().anyMatch(this::shouldIgnoreArgument)
                || Map.class.isAssignableFrom(clazz) && shouldIgnoreArgument(((Map<?, ?>) object).values())
                || object instanceof HttpServletRequest
                || object instanceof HttpServletResponse
                || object instanceof BindingResult;
    }

}
